2

一、Vue生命周期

浅级别部分

一个Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。

首先四个阶段很好记:

  • 创建 create
  • 挂载 mount
  • 更新 update
  • 销毁 destroy

每个阶段都有xx前(before+)、xx后(+ed)两部分,一共八个生命周期。

beforeCreate 在数据观测和初始化事件还未开始

created 完成一些属性和方法的运算,初始化事件

beforeMount 编译模板,把data里面的数据和模板生成html。但还没有挂载到页面上

mounted 渲染到html到页面,会进行ajax交互(dom渲染)

beforeUpdate 发生在虚拟DOM重新渲染和打补丁之前,可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。

updated 组件DOM已经更新,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

beforeDestroy 此时实例仍然完全可用

destroyed 所有的事件监听器、子实例会被销毁移除。该钩子在服务器端渲染期间不被调用。

1、生命周期中有多个事件钩子,帮助我们控制Vue实例的过程时更容易形成好的逻辑。

2、第一次加载页面触发(前四个) beforeCreate, created, beforeMount, mounted

待添加...

深级别部分 ###(水平不够先越过这里 这里只做记录)

关于create(初始化)
在初始化时,会调用以下代码,生命周期就是通过 callHook 调用的

Vue.prototype._init = function(options) {
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate') // 拿不到 props data
  initInjections(vm)
  initState(vm)
  initProvide(vm)
  callHook(vm, 'created')
}

beforeCreate 调用的时候,是获取不到 props 或者 data 中的数据的,因为这些数据的初始化都在 initState 中。

关于mount(挂载函数)

export function mountComponent {
    callHook(vm, 'beforeMount')
    // ...
    if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted')
    }
}

beforeMount 就是在挂载前执行的,然后开始创建 VDOM 并替换成真实 DOM,最后执行 mounted 钩子。这里会有个判断逻辑,如果是外部 new Vue({}) 的话,不会存在 $vnode ,所以直接执行 mounted 钩子了。如果有子组件的话,会递归挂载子组件,只有当所有子组件全部挂载完毕,才会执行根组件的挂载钩子。

关于update

function flushSchedulerQueue() {
  // ...
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before() // 调用 beforeUpdate
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' +
            (watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`),
          watcher.vm
        )
        break
      }
    }
  }
  callUpdatedHooks(updatedQueue)
}

function callUpdatedHooks(queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, 'updated')
    }
  }
}

上图还有两个生命周期没有说,分别为 activateddeactivated ,这两个钩子函数是 keep-alive 组件独有的。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

关于destroy

Vue.prototype.$destroy = function() {
  // ...
  callHook(vm, 'beforeDestroy')
  vm._isBeingDestroyed = true
  // remove self from parent
  const parent = vm.$parent
  if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
    remove(parent.$children, vm)
  }
  // teardown watchers
  if (vm._watcher) {
    vm._watcher.teardown()
  }
  let i = vm._watchers.length
  while (i--) {
    vm._watchers[i].teardown()
  }
  // remove reference from data ob
  // frozen object may not have observer.
  if (vm._data.__ob__) {
    vm._data.__ob__.vmCount--
  }
  // call the last hook...
  vm._isDestroyed = true
  // invoke destroy hooks on current rendered tree
  vm.__patch__(vm._vnode, null)
  // fire destroyed hook
  callHook(vm, 'destroyed')
  // turn off all instance listeners.
  vm.$off()
  // remove __vue__ reference
  if (vm.$el) {
    vm.$el.__vue__ = null
  }
  // release circular reference (##6759)
  if (vm.$vnode) {
    vm.$vnode.parent = null
  }
}

在执行销毁操作前会调用 beforeDestroy 钩子函数,然后进行一系列的销毁操作,如果有子组件的话,也会递归销毁子组件,所有子组件都销毁完毕后才会执行根组件的 destroyed 钩子函数。

二、Vue的双向绑定原理

数据劫持+发布-订阅者模式

广义双向绑定的描述就是 数据变化更新视图,视图变化更新数据
view->data:通过简单监听事件即可
data->view:通过Object.defineProperty() 来给属性设置 set(设置属性值时触发)和get(读取属性值时触发)函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了

Object.defineProperty()可以劫持各个属性的setter,getter,当数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

vue的数据双向绑定 整合ObserverCompileWatcher三者(不能完全理解的部分)

  • Observer来监听自己的model的数据变化
  • Compile来解析编译模板指令
  • watcher搭起observer和Compile之间的通信桥梁

example,使用js实现一个简单的双向绑定(理解)

<body>
    <div id="app">
    <input type="text" id="txt">
    <p id="show"></p>
</div>
</body>
<script type="text/javascript">
    var obj = {}
    Object.defineProperty(obj, 'txt', {
        get: function () {
            return obj
        },
        set: function (newValue) {
            document.getElementById('txt').value = newValue
            document.getElementById('show').innerHTML = newValue
        }
    })
    document.addEventListener('keyup', function (e) {
        obj.txt = e.target.value
    })
</script>

三、组件之间参数传递 + Vuex

父->子: prop方法,父组件在子组件标签里写好要传的数据,子组件在自己的data里接收
子->父: $emit方法,在自己的method里提交$emit
兄<->弟: 创建一个事件中心 eventBus

也可以直接用vuex,但在逻辑简单的单页应用里使用简单的store模式就可以

Vuex

专为 Vue.js 应用程序开发的状态管理模式,可以控制组件状态以你期望的方式进行更新

包含5个核心属性:
1、state 是一个用来储存数据的仓库
2、getter 对state中的数据计算,相当于state的计算属性
3、mutation 所有的数据更新操作最终必须由触发mutation中的方法实现
4、action 可写异步函数提交到mutation,通过store.dispath来分发action
5、module 把store拆分成易于项目业务和维护的模块
具体用法参考这里
$store.dispatch
$store.commit
待添加

四、关于Vue-router

Vue.js的官方路由管理器

分为 hash 模式和 history 模式

1、hash: url里#+#后面的部分就是hash的值,这部分的值不会被包括在http请求里进行提交,原理是 onhashchange 事件,hash是vue-router的默认方式(嫌丑才用history?)
2、history: 利用 history.pushState 、history.replaceState API 来完成 URL 跳转而无须重新加载页面。提交http请求时url全部会被提交,因此需要后台配合对每一个值进行应对,匹配一个404页面这样子。

2、Vue 路由中 route 和router 的区别

route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
router是“路由实例”对象包括了路由的跳转方法,钩子函数等。

3、上一条中的钩子函数(导航守卫)
在路由发生变化时需要进行一些特殊处理,就要用到钩子函数。Vue-router的钩子函数大致分为三类

1)全局钩子
beforeEachafterEach

const router = new VueRouter({
    mode: 'history',
    base: __dirname,
    routes: routerConfig
})

router.beforeEach((to, from, next) => {
    document.title = to.meta.title || 'demo'
    if (!to.query.url && from.query.url) {
        to.query.url = from.query.url
    }
    next()
})

router.afterEach(route => {

})

2)单个路由里面的钩子

如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫:


    const router = new VueRouter({
      routes: [
        {
          path: '/foo',
          component: Foo,
          beforeEnter: (to, from, next) => { 
            // 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
            // ...
          }
        }
      ]

3)组件内钩子
路由组件:直接定义在router中component处的组件。
在组件内,和data、method平级的方法使用

beforeRouteLeave(to, from, next) {
    /* 在路由独享守卫后调用 不!能!获取组件实例 `this`,组件实例还没被创建*/
    next()
},
beforeRouteEnter(to, from, next) {
    /* 在当前路由改变,但是该组件被复用时调用 可以访问组件实例 `this`
     举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
     由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。*/
    next()
},
beforeRouteUpdate(to, from, next) {
    /* 导航离开该组件的对应路由时调用,可以访问组件实例 `this`*/
    next()
},
computed: {},
method: {}

不记得用法了就看一下这个博客

三种路由钩子中都涉及到了三个参数:(先记住参数吧 啥也看不懂)


to: Route: 即将要进入的目标路由对象

from: Route: 当前导航正要离开的路由

next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

-> next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

->next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

->next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。

->next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调.


绿绿
33 声望3 粉丝